// 手写实现MyPromise源码
// 要求：尽可能还原Promise中的每一个API，并通过注释的方式描述思路和原理

// 状态: 开始或处理中
const PENDING = "PENDING";
// 状态: 完成
const FULFILLED = "FULFILLED";
// 状态: 失败
const REJECTED = "REJECTED";

class MyPromise {
  status = PENDING;
  onFulfilledHandlers = [];
  onRejectedHandlers = [];

  // 成功结果
  value = undefined;
  // 失败原因
  reason = undefined;

  constructor(executor) {
    if (typeof executor !== "function") {
      throw Error(`MyPromise resolver ${executor} is not a function`);
    }
    try {
      executor(this.resolve, this.reject);
    } catch (err) {
      this.reject(err);
    }
  }

  /**
   * 转化一个value为MyPromise
   * 若value为MyPromise的实例，则直接返回value，否则返回一个新的成功状态的MyPromise实例
   * @param {*} value
   */
  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve) => resolve(value));
  }

  /**
   * 转化一个value为MyPromise
   * 若value为MyPromise的实例，则直接返回value，否则返回一个新的失败状态的MyPromise实例
   * @param {*} reason
   */
  static reject(reason) {
    if (reason instanceof MyPromise) return reason;
    return new MyPromise((undefined, reject) => reject(reason));
  }

  /**
   * 返回一个新的MyPromise实例
   * 当其中任何一个MyPromise状态fulfilled或rejected，则返回这个MyPromise的返回结果（即赛跑，谁先到算谁赢）
   * 若同时返回，则取第一个
   * @param {*} promises
   */
  static race(promises) {
    promises = Array.from(promises);
    return new MyPromise((resolve, reject) => {
      let promise = null;
      while ((promise = promises.shift())) {
        MyPromise.resolve(promise).then(
          (res) => {
            resolve(res);
            return;
          },
          (err) => {
            reject(err);
            return;
          }
        );
      }
    });
  }

  /**
   * 返回一个新的MyPromise实例
   * 当所有的MyPromise状态fulfilled，则返回。（其中任何一个MyPromise状态rejected，则立马返回）
   * 若状态是fulfilled，则返回的结果是一个按传入的顺序组成的Array对象
   * @param {*} promises
   */
  static all(promises) {
    return handlePromises(promises, true);
  }

  /**
   * 返回一个新的MyPromise实例
   * 当所有的MyPromise状态fulfilled或rejected，则返回。
   * 返回的结果是一个按传入的顺序组成的Array对象
   * @param {*} promises
   */
  static allSettled(promises) {
    return handlePromises(promises, false);
  }

  /**
   * 返回一个新的MyPromise实例
   * 当其中任何一个MyPromise状态fulfilled，则返回该MyPromise
   * 若所有MyPromise状态都rejected，则返回一个AggregateError类型的实例
   * @param {*} promises
   */
  static any(promises) {
    promises = Array.from(promises);
    return new MyPromise((resolve, reject) => {
      let count = 0;
      const len = promises.length;
      for (let i = 0; i < len; i++) {
        const value = promises[i];
        if (!(value instanceof MyPromise)) {
          resolve(value);
          break;
        }
        value.then(
          (res) => resolve(res),
          () => {
            count++;
            if (count === len) {
              reject("AggregateError: No Promise in MyPromise.any was resolved");
            }
          }
        );
      }
    });
  }

  /**
   * 成功处理
   * @param {*} value
   */
  resolve = (value) => {
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    let handler = null;
    while ((handler = this.onFulfilledHandlers.shift())) handler();
  };

  /**
   * 失败处理
   * @param {*} reason
   */
  reject = (reason) => {
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    this.reason = reason;
    let handler = null;
    while ((handler = this.onRejectedHandlers.shift())) handler();
  };

  // 异步回调
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };
    const nextPromise = new MyPromise((resolve, reject) => {
      const wrapper = (fn) =>
        setTimeout(() => {
          try {
            fn();
          } catch (err) {
            reject(err);
          }
        }, 0);
      switch (this.status) {
        case FULFILLED:
          wrapper(() => {
            const result = onFulfilled(this.value);
            resolvePromise(nextPromise, result, resolve, reject);
          });
          break;
        case REJECTED:
          wrapper(() => {
            const result = onRejected(this.reason);
            resolvePromise(nextPromise, result, resolve, reject);
          });
          break;
        default:
          this.onFulfilledHandlers.push(() => {
            wrapper(() => {
              const result = onFulfilled(this.value);
              resolvePromise(nextPromise, result, resolve, reject);
            });
          });
          this.onRejectedHandlers.push(() => {
            wrapper(() => {
              const result = onRejected(this.reason);
              resolvePromise(nextPromise, result, resolve, reject);
            });
          });
          break;
      }
    });
    return nextPromise;
  }

  // 捕获异常时执行
  catch(errorHandler) {
    return this.then(null, errorHandler);
  }

  // 最终一定会执行
  finally(finalHandler) {
    const handler = () => MyPromise.resolve(finalHandler());
    return this.then(
      (value) => {
        handler().then(() => value);
      },
      (err) => {
        handler().then(() => {
          throw err;
        });
      }
    );
  }
}

function resolvePromise(nextPromise, result, resolve, reject) {
  if (result === nextPromise) {
    return reject(new Error("Chaining cycle detected for promise #<Promise>"));
  }
  if (result instanceof MyPromise) {
    return result.then(resolve, reject);
  }
  resolve(result);
}

function handlePromises(promises, isBreakup) {
  promises = Array.from(promises);
  return new MyPromise((resolve, reject) => {
    let count = 0;
    const result = [];
    const len = promises.length;
    const append = (res, idx) => {
      result[idx] = res;
      count++;
      if (count === len) {
        resolve(result);
      }
    };
    for (let i = 0; i < len; i++) {
      const value = promises[i];
      if (value instanceof MyPromise) {
        value.then(
          (res) => append(res, i),
          (err) => {
            if (isBreakup) {
              reject(err);
            } else {
              append(err, i);
            }
          }
        );
      } else {
        append(value, i);
      }
    }
  });
}

module.exports = MyPromise;
